/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package steg.sekret;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import steg.steki.StegException;
import steg.utils.SkrotSha1;
import steg.utils.StringZipper;

/**
 *  Klasa reprezentująca miejsce pliku lub treść wiadomości i sposób zapisu tajnej informacji.
 *
 * @author 
 */
public class Sekret {


// ------------------------------- ZIENNE PRYWATNE -----------------------------
    //-- stałe
    /*private*/ static final byte[] ZNACZNIK = { (byte)0x58,(byte)0x58,(byte)0x58,(byte)0x58};
    //-- czy
    private boolean czySekretAsText = false;
    private boolean czyZip = false;
    private boolean czyZipHaslo = false;
    private boolean czyHaslo = false;
    private boolean czyZnacznik = false;
    //-- zmienne
    private File skPlik;
    private DataInputStream dataIn;
    private InputStream in;
    private byte[] skWiadomosc;
    private byte[] znacznik;
    private String skWiadomoscString;
    private boolean czyKoniec = false;
    
    private char[] zipHaslo = new char[0];
    //private char[] sekretHaslo = new char[0];
    private byte[] sekretHaslo= new byte[0];
    private int skBitowNaSample;

    //long pozWiadomosci = 0;
// ------------------------------- ZIENNE PUBLICZNE
    //-- czy
    //-- zmienne

// ------------------------------- KONSTRUKTORY

    public Sekret() {

    }




// ******************************** PUBLICZNE **********************************
// *****************************************************************************
// ------------------------------- FUNKCJE KRYTYCZNE ---------------------------

    public void start() throws IOException, StegException {

        if(this.isCzyHaslo() && !this.isZnacznik())
            throw new StegException( new Exception(), StegException.BLAD_INNY, "Hasło nie może zostać wpisane bez znacznika !" );




        // konstrukcja znacznika tagu
        // 4 byte to TAG
        // 1 kolejny to
        //      - 1 bit, pusty 0, coś funkcja radix bez tego miesza :/
        //      - 2 bit, czy text 1 is true
        //      - 3 bit, czy haslo 1 is true
        //      - 4 bit, czy zip 1 is true
        //      - 5,6,7,8 bit, ile na sample <1,8>
        //      
        // 8 byte to dl pliku

        // = 13 bytów

        // a) znacznik rob zawsze, potem sie go jak co nie wspisze
        // b) ze string w byte, chyba najprostsza droga
        String z = "";

        // pusty 0
        z+="0";

        if(this.isSecretAsText()) z += "1"; else z+= "0";
        if(this.isCzyHaslo()) z += "1"; else z+= "0";
        if(this.isZip()) z += "1"; else z+= "0";

        // konwersja 4 bitów z skBitowNaSample
        //od 1 do 8 to je 1 10 11 100 101 110 111 1000
        String temp = Integer.toBinaryString( this.getBitowNaSample() );

        // serio nie ma nic lepszego na google i wogile :/
        if(temp.length()==1)
            z+= "000"+temp;
        else if(temp.length()==2)
            z+= "00"+temp;
        else if(temp.length()==3)
            z+= "0"+temp;
        else if(temp.length()==3)
            z+= "0"+temp;
        else if(temp.length()==4)
            z+= temp;






        byte zb = Byte.parseByte(z, 2);

        // inicjacja plikuw
        long dl;
        if(!this.isSecretAsText()) {
            in = new FileInputStream( skPlik );
            dataIn = new DataInputStream(in);
            tempBit = dataIn.readByte();
            dl = this.getFile().length();
        }else{
            dl = skWiadomosc.length;
        }
        


        //tworzenie tagu
        znacznik = new byte[13];
        int i=0;
        for (byte elem : ZNACZNIK) {
            znacznik[i++] = elem;
        }

        znacznik[i++] = zb;

        for(int j= 0; j < 8; j++){
            znacznik[i++] = (byte)(dl >>> (j * 8));
        }
        // ---- KONIEC znacznika

    }


    public void close() throws IOException {

        if(!this.isSecretAsText())
            dataIn.close(); // zamknij plik 

        //wszelkie wartości pomocnicze na default !
        flag_haslo_czy = true;
        flag_haslo = true;
        pozInWiadomoscHasla = 0;
        flag_znacznik = true;
        tempBit=0;
        pozInWiadomosc = 0;

        setEOF(false);

    }

    /**
     * Pobiera X kolejnych bitów informacji zapisanych w byte na początku i
     * zakończonych 0 jeśli bitów jest mniej niż 8.
     * Np
     *  bns = 3
     *  return = 00000<b>101</b>;
     *
     * Jeśli wiadomość została przekazana w całości to zwracane jest 1.
     * @param bns
     * @return
     */
    public byte getNextMessageBit() throws IOException {

        if(isEOF()) return 0; //wartość bez znaczenia, ot dodatkowa informacja

        // czy znacznik czy juz wiadomosc
        if(this.isZnacznik() && flag_znacznik) {

            return getNextBitFromZnacznik();

        }

        // czy wpisujemy haslo czy dane ?
        if(this.isCzyHaslo() && flag_haslo) {

            if(flag_haslo_czy) {
                flag_haslo_czy = false;
                return getNextBitFromHaslo();
            }else
                flag_haslo_czy = true;

        }



        if(this.isSecretAsText()) {

            return getNextBitFromText();
        }else{

            return getNextBitFromFile();
        }

    }

// ------------------------------- FUNKCJE DODATKOWE ---------------------------
    public String getPodsumowanie() {
        String tagi = "";

        String x;
        if(this.isSecretAsText())
            x = "ASCII";
        else
            x = this.getFile().getName();

        tagi += "Wiadomości: " + x +"<br/>";
        tagi += "Ukryty plik: " + this.isSecretAsText() +"<br/>";
        tagi += "Hasło: " + "***" +"<br/>";
        tagi += "ZIP: " + this.isZip() +"<br/>";
        tagi += "ZIP hasło: " + "***" +"<br/>";
        tagi += "Znacznik: " + this.isZnacznik() +"<br/>";


        return tagi;

    }

// ------------------------------- FUNKCJE dziwne ------------------------------

// ******************************** PRYWATNE ***********************************
// *****************************************************************************
// ------------------------------- FUNKCJE KRYTYCZNE
    private byte getNextBitFromText() {

        //1.get wiadomosc acii kod
        byte ch = skWiadomosc[ (int) (pozInWiadomosc / 8) ];
        int chasci = (int) ch;
//System.out.println("ch:"+Integer.toBinaryString(chasci));

        //2. ustaw maske
        //int maska = 0xFFFFFF00;//
        int maska = 0x000000FF;
        long poz = (long) (pozInWiadomosc % 8);

            //ustaw 1ki na miejsce oczekiwanego bitu reszte 0 wykasuja
        maska >>= (8-skBitowNaSample);  // określamy ilość 1 ek w masce
        maska <<= (8-poz-skBitowNaSample); // ustawiamy pozycje maski

            //kasowanie
        chasci &= maska;
            //maska do przodu, to co chcemy jest teraz najmlodszymi bitami
        chasci >>= (8-poz-skBitowNaSample);

        //3. dodawanie 1 wkolo maski, pozwoli to na przemnożenie byte msg z byte steg i zachowaniu bitow nie stegowalnych
        /*maska = 0xFFFFFFFF;
        maska <<= bns;
        chasci |= maska;*/// NIE POTRZEBNE !!! 0 mają być, bo dane dodanjemy!!!

        //4. przesuniecie wskaznika i zwrocenie bitu msg
        pozInWiadomosc+=skBitowNaSample;

        //0. czy to już all
        if((int)(pozInWiadomosc/8)>=skWiadomosc.length )
            setEOF(true);

        return (byte)(chasci);// ustawiamy na sam początek

    }

    private boolean flag_haslo_czy = true;  // czy teraz dane wlasciwe czy haslo ?
    private boolean flag_haslo = true;
    private float pozInWiadomoscHasla = 0; // ile tam juz tego hasla pobrano
    /**
     * Haslo wpisywane jest jaki pierwsza dana i na przemian z daymi, przeplatanie
     * Haslo wymaga znacznika, nie może być hasło bez znacnika!!!
     * @return
     */
    private byte getNextBitFromHaslo() {

        //1.get wiadomosc acii kod
        byte ch = sekretHaslo[ (int) (pozInWiadomoscHasla / 8) ];
        int chasci = (int) ch;
//System.out.println("ch:"+Integer.toBinaryString(chasci));

        //2. ustaw maske
        //int maska = 0xFFFFFF00;//
        int maska = 0x000000FF;
        long poz = (long) (pozInWiadomoscHasla % 8);

            //ustaw 1ki na miejsce oczekiwanego bitu reszte 0 wykasuja
        maska >>= (8-skBitowNaSample);  // określamy ilość 1 ek w masce
        maska <<= (8-poz-skBitowNaSample); // ustawiamy pozycje maski

            //kasowanie
        chasci &= maska;
            //maska do przodu, to co chcemy jest teraz najmlodszymi bitami
        chasci >>= (8-poz-skBitowNaSample);

        //3. dodawanie 1 wkolo maski, pozwoli to na przemnożenie byte msg z byte steg i zachowaniu bitow nie stegowalnych
        /*maska = 0xFFFFFFFF;
        maska <<= bns;
        chasci |= maska;*/// NIE POTRZEBNE !!! 0 mają być, bo dane dodanjemy!!!

        //4. przesuniecie wskaznika i zwrocenie bitu msg
        pozInWiadomoscHasla+=skBitowNaSample;

        //0. czy to już all
        if((int)(pozInWiadomoscHasla/8)>=sekretHaslo.length ) {
            flag_haslo = false;
            pozInWiadomoscHasla = 0; // wracamy na 0, bo teraz czas na wiadomosc wlasciwa!
        }


        return (byte)(chasci);// ustawiamy na sam początek

    }


    private boolean flag_znacznik = true;
    private byte getNextBitFromZnacznik() {

        //1.get wiadomosc acii kod
        byte ch = znacznik[ (int) (pozInWiadomosc / 8) ];
        int chasci = (int) ch;
//System.out.println("ch:"+Integer.toBinaryString(chasci));

        //2. ustaw maske
        //int maska = 0xFFFFFF00;//
        int maska = 0x000000FF;
        long poz = (long) (pozInWiadomosc % 8);

            //ustaw 1ki na miejsce oczekiwanego bitu reszte 0 wykasuja
        maska >>= (8-skBitowNaSample);  // określamy ilość 1 ek w masce
        maska <<= (8-poz-skBitowNaSample); // ustawiamy pozycje maski

            //kasowanie
        chasci &= maska;
            //maska do przodu, to co chcemy jest teraz najmlodszymi bitami
        chasci >>= (8-poz-skBitowNaSample);

        //3. dodawanie 1 wkolo maski, pozwoli to na przemnożenie byte msg z byte steg i zachowaniu bitow nie stegowalnych
        /*maska = 0xFFFFFFFF;
        maska <<= bns;
        chasci |= maska;*/// NIE POTRZEBNE !!! 0 mają być, bo dane dodanjemy!!!

        //4. przesuniecie wskaznika i zwrocenie bitu msg
        pozInWiadomosc+=skBitowNaSample;

        //0. czy to już all
        if((int)(pozInWiadomosc/8)>=znacznik.length ) {
            flag_znacznik = false;
            pozInWiadomosc = 0; // wracamy na 0, bo teraz czas na wiadomosc wlasciwa!
        }
            

        return (byte)(chasci);// ustawiamy na sam początek

    }

    
    private byte tempBit=0;
    float pozInWiadomosc = 0;
    private byte getNextBitFromFile() throws IOException {



        //1.get wiadomosc acii kod
        if(pozInWiadomosc>=8) {
            pozInWiadomosc=0;
            
            //0. czy to już all
            if(dataIn.available()==0) {
                setEOF(true);
                return 1; // wartość bez znaczenia, ale bedzie można poznać przy debugowaniu czy błąd
            }
            tempBit = dataIn.readByte();
        }
        
        int chasci = (int) tempBit;
//System.out.println("ch:"+Integer.toBinaryString(chasci));

        //2. ustaw maske
        //int maska = 0xFFFFFF00;//
        int maska = 0x000000FF;
        long poz = (long) (pozInWiadomosc % 8);

            //ustaw 1ki na miejsce oczekiwanego bitu reszte 0 wykasuja
        maska >>= (8-skBitowNaSample);  // określamy ilość 1 ek w masce
        maska <<= (8-poz-skBitowNaSample); // ustawiamy pozycje maski

            //kasowanie
        chasci &= maska;
            //maska do przodu, to co chcemy jest teraz najmlodszymi bitami
        chasci >>= (8-poz-skBitowNaSample);

        //3. dodawanie 1 wkolo maski, pozwoli to na przemnożenie byte msg z byte steg i zachowaniu bitow nie stegowalnych
        /*maska = 0xFFFFFFFF;
        maska <<= bns;
        chasci |= maska;*/// NIE POTRZEBNE !!! 0 mają być, bo dane dodanjemy!!!

        //4. przesuniecie wskaznika i zwrocenie bitu msg
        pozInWiadomosc+=skBitowNaSample;


        return (byte)(chasci);// ustawiamy na sam początek








/*

        // stworz maske
        byte maska = (byte) 0x7F;
        if(skBitowNaSample!=8)
            maska >>= 7-skBitowNaSample;
        else
            maska = (byte) 0xFF;

        //1.get wiadomosci nowy bajt
        if(licznikBitow<0) {
            //0. czy to już all
            if(dataIn.available()==0) {
                setEOF(true);
                return 1;
            }

            licznikBitow = 8-skBitowNaSample;
            tempBit = dataIn.readByte();
        }

        //pobranie bitow
        maska <<= licznikBitow;
        tempBit &= maska;

        //przesuniecie pobranych bitow
        tempBit >>= licznikBitow;

        licznikBitow-=skBitowNaSample;

        return tempBit;
*/
    }
// ------------------------------- FUNKCJE POMOCNICZE

    public void setEOF(boolean b) {
        czyKoniec = b;
    }

    public boolean isEOF() {
        return czyKoniec;
    }


    public void setBitowNaSample(int b) {

        if(b%2!=0 && 1!=b)
            throw new UnsupportedOperationException("Not supported yet. TYLKO PARZYSTE i 1! b="+b);

        skBitowNaSample = b;
    }

    public int getBitowNaSample() {
        return skBitowNaSample;
    }

    /**
     * Ustawia wiadomośc textową, w zależności od opcji odrazu kompresuje.
     * @param b
     * @throws java.io.IOException
     */
    public void setWiadomosc(String b) throws IOException {

        if(b.length()>1123) throw new IOException("Plik jest za duży");

        skWiadomoscString = b;


        // jesli zip to zip, na co tu czekac
        if(this.isZip())
            skWiadomosc = StringZipper.zipStringToBytes(b);
        else
            skWiadomosc = b.getBytes();

    }

    public String getWiadomosc() {
        return skWiadomoscString;
    }

    public byte[] getWiadomoscBytes() {
        return skWiadomosc;
    }

    public void setSecretAsText(boolean b) {
        czySekretAsText = b;
    }

    public boolean isSecretAsText() {
        return czySekretAsText;
    }

    public void setFile(File b) {
        skPlik = b;
        //setSecretAsText(false);
    }

    public File getFile() {
        return skPlik;
    }


    public boolean isZip() {
        return czyZip;
    }

    public void setCzyZip(boolean czyZip) {
        this.czyZip = czyZip;
    }

    public boolean isZipHaslo() {
        /*return czyZipHaslo;*/throw new UnsupportedOperationException("Not supported yet. Brak biblioteki!");
    }

    public void setCzyZipHaslo(boolean czyZipHaslo) {
        this.czyZipHaslo = czyZipHaslo;throw new UnsupportedOperationException("Not supported yet. Brak biblioteki!");
    }

    public char[] getZipHaslo() {
        return zipHaslo;
    }

    public void setZipHaslo(char[] zipHaslo) {
        this.zipHaslo = zipHaslo;
    }


    public boolean isZnacznik() {
        return czyZnacznik;
    }

    public void setZnacznik(boolean czyZnacznik) {
        this.czyZnacznik = czyZnacznik;
    }

    public byte[] getSekretHaslo() {
        return sekretHaslo;
    }

    public void setSekretHaslo(byte[] sekretHaslo) {
        this.sekretHaslo = sekretHaslo;
    }

    public void setSekretHaslo(char[] sHch ) throws StegException {

        // to bedzie klamac ale w dupie z tym
          // tworzenie chasła

        byte[] tempbyte = new byte[sHch.length]; // 4 integer czyli lendoklejony do hasla na poczatku
        int i=0;
        for (char elem : sHch) {
            tempbyte[i++] = (byte)elem;
        }

        this.sekretHaslo = tempbyte;
        sHch = new char[0]; //haslo char, zgodnie z zaleceniem kasujemy

        // haszujemymy
        try{

        // tworzenie chasła
            sekretHaslo = SkrotSha1.SHA1( new String(sekretHaslo) );

        } catch (NoSuchAlgorithmException ex) {
            throw new StegException( ex, StegException.BLAD_INNY, "Bład skrótu SHA-1!" );
        } catch (UnsupportedEncodingException ex) {
            throw new StegException( ex, StegException.BLAD_INNY, "Bład skrótu tworzenia SHA-1!" );
        }


    }

    public boolean isCzyHaslo() {
        return czyHaslo;
    }

    public void setCzyHaslo(boolean czyHaslo) {
        this.czyHaslo = czyHaslo;
    }




}
